//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Research.ScientificWorkflow.NetCDF.CSharpAPI;
using Microsoft.Research.ScientificWorkflow.Activities;
using WorkflowData;

namespace Microsoft.Research.ScientificWorkflow.Helpers
{
	interface IHyperCubeTransform
	{
		HyperCube Transform(params object[] parameters);
	}

    //class CastingTransform : IHyperCubeTransform
    //{
    //    #region IHyperCubeTransform Members

    //    public NetCDFFile Transform(params object[] parameters)
    //    {
    //        HyperCube cube = inputCube.HyperCube;

    //        if (parameters.Length < 2)
    //            throw new ArgumentException("Three parameters are required");

    //        string variableToCast = parameters[0] as string;
    //        if (variableToCast == null)
    //            throw new ArgumentException("string object requried as second parameter");

    //        NetCDFDataType outType = (NetCDFDataType)parameters[1];

    //        // 1. Create a clone of input schema
    //        // 2. chagne the data type of variable (based on index)
    //        // 3. create new hyper cube
    //        HyperCubeSchema newSchema = new HyperCubeSchema(cube.Schema.ColumnCount);
    //        for (int i = 0; i < cube.Schema.ColumnCount; i++)
    //        {
    //            newSchema.SetName(i, cube.Schema.GetColumnName(i));
    //            newSchema.SetType(i, cube.Schema.GetColumnType(i));
    //            newSchema.SetDescription(i, cube.Schema.GetColumnDescription(i));
    //        }

    //        int index;
    //        try 
    //        {
    //            index = cube.Schema.LookupColumnIndex(variableToCast);
    //        }
    //        catch (Exception )
    //        {
    //            throw;
    //        }

    //        newSchema.SetType(index, TypeMapper.GetClrType(outType));
    //        HyperCube newCube = new HyperCube(newSchema,
    //                                            cube.GetAxisLength(HyperCube.Axis.X), cube.GetAxisLength(HyperCube.Axis.Y),
    //                                            cube.GetAxisLength(HyperCube.Axis.Z), cube.GetAxisLength(HyperCube.Axis.T));
    //                                            /*cube.GetDimensionName(HyperCube.Axis.X),
    //                                            cube.GetDimensionName(HyperCube.Axis.Y),
    //                                            cube.GetDimensionName(HyperCube.Axis.Z),
    //                                            cube.GetDimensionName(HyperCube.Axis.T)
    //                                            );*/

    //        // 4. populate all datapoints

    //        int xlen = cube.GetDataAxisLength(HyperCube.Axis.X);
    //        int ylen = cube.GetDataAxisLength(HyperCube.Axis.Y);
    //        int zlen = cube.GetDataAxisLength(HyperCube.Axis.Z);
    //        int tlen = cube.GetDataAxisLength(HyperCube.Axis.T);
    //        int clen = cube.Schema.ColumnCount;
    //        object data = null;

    //        for (int x = 0; x < xlen; x++)
    //        {
    //            for (int y = 0; y < ylen; y++)
    //            {
    //                for (int z = 0; z < zlen; z++)
    //                {
    //                    for (int t = 0; t < tlen; t++)
    //                    {
    //                        for (int col = 0; col < clen; col++)
    //                        {
    //                                data = cube[x, y, z, t, col];
    //                                if (data != null)
    //                                {
    //                                    if (col == index)
    //                                    {
    //                                        switch (outType)
    //                                        {
    //                                            case NetCDFDataType.NcByte:
    //                                                newCube.SetData(Convert.ToByte(data), x, y, z, t, col);
    //                                                break;
    //                                            case NetCDFDataType.NcChar:
    //                                                newCube.SetData(Convert.ToChar(data), x, y, z, t, col);
    //                                                break;
    //                                            case NetCDFDataType.NcShort:
    //                                                newCube.SetData(Convert.ToInt16(data), x, y, z, t, col);
    //                                                break;
    //                                            case NetCDFDataType.NcInt:
    //                                                newCube.SetData(Convert.ToInt32(data), x, y, z, t, col);
    //                                                break;
    //                                            //case NetCDFDataType.NcLong:
    //                                            //    newCube.SetData(Convert.ToInt64(data), x, y, z, t, col);
    //                                            //    break;
    //                                            case NetCDFDataType.NcFloat:
    //                                                newCube.SetData(Convert.ToSingle(data), x, y, z, t, col);
    //                                                break;
    //                                            case NetCDFDataType.NcDouble:
    //                                                newCube.SetData(Convert.ToDouble(data), x, y, z, t, col);
    //                                                break;
    //                                        }
    //                                    }
    //                                    else
    //                                    {
    //                                        newCube.SetData(data, x, y, z, t, col);
    //                                    }
    //                                }
    //                        }
    //                    }
    //                }
    //            }
    //        }

    //        NetCDFFile netCDFToReturn = new NetCDFFile();
    //        netCDFToReturn.HyperCube = newCube;
    //        netCDFToReturn.NetCDFMetadata = inputCube.NetCDFMetadata;
            
    //        NcMetaData ncMeta = NetCDFFile.GetMetaData(netCDFToReturn.NetCDFMetadata);
    //        NcVar var = ncMeta.GetVariable(variableToCast);
    //        var.Type = (NcType) outType;
    //        netCDFToReturn.NcMetaData = ncMeta;

    //        return netCDFToReturn;
    //    }

    //    #endregion
    //}

	class CombineHyperCubesTransform : IHyperCubeTransform
	{
		#region IHyperCubeTransform Members

		public HyperCube Transform(params object[] parameters)
		{
			if (parameters.Length < 3)
				throw new ArgumentException("Two parameters are required");

    		// Get the two HyperCubes from the param list
            HyperCube cube1 = parameters[0] as HyperCube;
            if (cube1 == null)
                throw new ArgumentException("Unexpected arguments for transform");

            HyperCube cube2 = parameters[1] as HyperCube;
            if (cube2 == null)
                throw new ArgumentException("Unexpected arguments for transform");

            string[] variableNames = parameters[2] as string[];
			if (variableNames == null)
				throw new ArgumentException("string[] is requried as third parameter.");

			// Assumtions:- Combining two cubes
			//			1. The variable mapped on TDimension is a Record Variable
			//			2. If the input cubes has diff lengths for FIXED dimensions then throw error and exit
			//			3. All the variables in passed in variableNames, must be present i nboth cubes.
			// Steps:
			// 1. Create a new schema with variables in the variableNames array.
			// 2. Create HC with x,y,z as same length but increased the length of T dim
			// 3. Copy values one by one....
			// 4. Update the metadata - this will be used to write values back to NC file.
			
			HyperCubeSchema firstSchema  = cube1.Schema;
			HyperCubeSchema secondSchema = cube2.Schema;

			// 2. Validations
            if (cube1.GetAxisLength(HyperCube.Axis.X) != cube2.GetAxisLength(HyperCube.Axis.X) ||
                cube1.GetAxisLength(HyperCube.Axis.Y) != cube2.GetAxisLength(HyperCube.Axis.Y) ||
                cube1.GetAxisLength(HyperCube.Axis.Z) != cube2.GetAxisLength(HyperCube.Axis.Z)
				)
			{
				throw new ArgumentException("Two hyper cubes must match in terms of dimension lengths for X, Y and Z.");
			}

			// Create a new schema - and validation/comparision of both schemas
			// 1. All the variables in passed in variableNames, must be present in both cubes.
            for (int i = 0; i < variableNames.Length; i++)
            {
                int indexInfirst = firstSchema.LookupColumnIndex(variableNames[i]);
                int indexInSecond = secondSchema.LookupColumnIndex(variableNames[i]);
                if (indexInfirst >= 0 && indexInSecond >= 0)
                {
                    if (firstSchema.GetColumnType(indexInfirst) != secondSchema.GetColumnType(indexInSecond))
                    {
                        throw new ArgumentException("Datatypes for variable '" + variableNames[i] + "' do not match in both cubes");
                    }
                }
            }
            HyperCubeSchema resultSchema = cube1.Schema.Clone();

			// 2. Determine the size of the HyperCube, specially the TDimension
			int xLen = cube1.GetAxisLength(HyperCube.Axis.X);
            int yLen = cube1.GetAxisLength(HyperCube.Axis.Y);
            int zLen = cube1.GetAxisLength(HyperCube.Axis.Z);
            int tLen1 = cube1.GetAxisLength(HyperCube.Axis.T);
			int tLen2 = cube2.GetAxisLength(HyperCube.Axis.T);

			HyperCube resultHC = new HyperCube(resultSchema, xLen, yLen, zLen, tLen1 + tLen2);
            resultHC.XDimensionValues = cube1.XDimensionValues;
            resultHC.YDimensionValues = cube1.YDimensionValues;
            resultHC.ZDimensionValues = cube1.ZDimensionValues;
            resultHC.TDimensionValues = cube1.TDimensionValues;
            resultHC.TDimensionValues.AddRange(cube2.TDimensionValues);

			// Now merge both cubes
			// 3. Populate the data in the output HyperCube
			for (int i = 0; i < variableNames.Length; i++)
			{
				//fill the first HC
				int indexInfirst = firstSchema.LookupColumnIndex(variableNames[i]);
				for (int x = 0; x < xLen; x++)
				{
					for (int y = 0; y < yLen; y++)
					{
						for (int z = 0; z < zLen; z++)
						{
							for (int t = 0; t < tLen1; t++)
							{
								//result[x, y, z, t][0] = InputHyperCube[x, y, z, t][oldIndex];
                                HyperCubeDataPoint data = cube1[x, y, z, t];
								if (data != null && data[indexInfirst] != null)
									resultHC.SetData(data[indexInfirst], x, y, z, t, i);
							}
						}
					}
				}

				//Fill the second HC in the rest of the values.
				int indexInSecond = secondSchema.LookupColumnIndex(variableNames[i]);
				for (int x = 0; x < xLen; x++)
				{
					for (int y = 0; y < yLen; y++)
					{
						for (int z = 0; z < zLen; z++)
						{
							for (int t = 0; t < tLen2 ; t++)
							{
								//result[x, y, z, t][0] = InputHyperCube[x, y, z, t][oldIndex];
								HyperCubeDataPoint data = cube2[x, y, z, t];
								if (data != null && data[indexInSecond] != null)
									resultHC.SetData(data[indexInSecond], x, y, z, tLen1 + t, i);
							}
						}
					}
				}
            }

            return resultHC;
		}

		#endregion
	}

    class AlgebraOperationTransform : IHyperCubeTransform
    {
        #region IHyperCubeTransform Members

        public HyperCube Transform(params object[] parameters)
        {
            #region Input arguments validations

            if (parameters.Length < 4)
                throw new ArgumentException("Incorrect arguments for Algebra Transform");

            HyperCube cube1 = parameters[0] as HyperCube;
            if (cube1 == null)
                throw new ArgumentException("Unexpected arguments for transform");

            HyperCube cube2 = parameters[1] as HyperCube;
            if (cube2 == null)
                throw new ArgumentException("Unexpected arguments for transform");
            
            string[] variableNames = parameters[2] as string[];
            if (variableNames == null)
                throw new ArgumentException("string[] is requried as third parameter.");

            AlgebraOperations operationToDo = (AlgebraOperations)parameters[3];

            if (variableNames == null)
                throw new ArgumentException("string[] is requried as third parameter.");

            #endregion

            // 1. Validations.
            // 2. Create a new schema with variables in the variableNames array.
            // 3. Create new hyper cube.
            // 5. Return - (No need to update metadata).

            #region 1. Validations (both HyperCubes must be of same size)
            if (cube1.GetAxisLength(HyperCube.Axis.X) != cube2.GetAxisLength(HyperCube.Axis.X) ||
                cube1.GetAxisLength(HyperCube.Axis.Y) != cube2.GetAxisLength(HyperCube.Axis.Y) ||
                cube1.GetAxisLength(HyperCube.Axis.Z) != cube2.GetAxisLength(HyperCube.Axis.Z) ||
                cube1.GetAxisLength(HyperCube.Axis.T) != cube2.GetAxisLength(HyperCube.Axis.T)
                )
            {
                throw new ArgumentException("Two hyper cubes must match in terms of dimension lengths for X, Y, Z and T axies");
            }
            #endregion

            #region 2. Create a new schema - and validation/comparision of both schemas

            HyperCubeSchema firstSchema = cube1.Schema;
            HyperCubeSchema secondSchema = cube2.Schema;

            // All the variables in passed in variableNames, must be present in both cubes and their Types must match.
            //HyperCubeSchema resultSchema = new HyperCubeSchema(variableNames.Length);
            for (int i = 0; i < variableNames.Length; i++)
            {
                // If variable name not found the 'LookupColumnIndex' throws exception, so no need to check
                int indexInfirst = firstSchema.LookupColumnIndex(variableNames[i]);
                int indexInSecond = secondSchema.LookupColumnIndex(variableNames[i]);
                if (indexInfirst >= 0 && indexInSecond >= 0)
                {
                    if (firstSchema.GetColumnType(indexInfirst) != secondSchema.GetColumnType(indexInSecond))
                    {
                        throw new ArgumentException("Datatypes for variable '" + variableNames[i] + "' do not match in both cubes");
                    }
                }

                // Only numeric types are allowed!
                Type t = firstSchema.GetColumnType(indexInfirst);
                if (!(t == typeof(byte) || t == typeof(short) || t == typeof(int) || t == typeof(float) || t == typeof(double)))
                {
                    throw new ArgumentException("The varaible '" + variableNames[i] + "' is not or type numeric.");
                }
            }
            // Clone the schema if there are no errors
            HyperCubeSchema resultSchema = cube1.Schema.Clone();

            #endregion

            #region 3. Create the new HC

            // Determine the size of the HyperCube
            int xLen = cube1.GetAxisLength(HyperCube.Axis.X);
            int yLen = cube1.GetAxisLength(HyperCube.Axis.Y);
            int zLen = cube1.GetAxisLength(HyperCube.Axis.Z);
            int tLen1 = cube1.GetAxisLength(HyperCube.Axis.T);
            //int tLen2 = cube2.GetAxisLength(HyperCube.Axis.T);

            HyperCube resultHC = new HyperCube(resultSchema, xLen, yLen, zLen, tLen1);
            #endregion

            #region 4. Write the dimension values as is w/o doing a difference

            resultHC.XDimensionValues = cube1.XDimensionValues;
            resultHC.YDimensionValues = cube1.YDimensionValues;
            resultHC.ZDimensionValues = cube1.ZDimensionValues;
            resultHC.TDimensionValues = cube1.TDimensionValues;

            //for (int i = 0; i < variableNames.Length; i++)
            //{
            //    int colIndex = cube1.Schema.LookupColumnIndex(variableNames[i]);
            //    if (variableNames[i] == cube1.Schema.XDimensionName)
            //    {
            //        for (int x = 0; x < xLen; x++)
            //        {
            //            resultHC.SetData(cube1[x, 0, 0, 0][colIndex], x, 0, 0, 0, colIndex);
            //        }
            //        variableNames[i] = "";
            //    }
            //    else if (variableNames[i] == cube1.Schema.YDimensionName)
            //    {
            //        for (int y = 0; y < yLen; y++)
            //        {
            //            resultHC.SetData(cube1[0, y, 0, 0][colIndex], 0, y, 0, 0, colIndex);
            //        }
            //        variableNames[i] = "";
            //    }
            //    else if (variableNames[i] == cube1.Schema.ZDimensionName)
            //    {
            //        for (int z = 0; z < zLen; z++)
            //        {
            //            resultHC.SetData(cube1[0, 0, z, 0][colIndex], 0, 0, z, 0, colIndex);
            //        }
            //        variableNames[i] = "";
            //    }
            //    else if (variableNames[i] == cube1.Schema.TDimensionName)
            //    {
            //        for (int t = 0; t < tLen1; t++)
            //        {
            //            resultHC.SetData(cube1[0, 0, 0, t][colIndex], 0, 0, 0, t, colIndex);
            //        }
            //        variableNames[i] = "";
            //    }
            //}
            #endregion

            #region 5. Populate the data in the output HyperCube, Do this for each variable in the Variables list.

            for (int i = 0; i < variableNames.Length; i++)
            {
                if (variableNames[i] == "")
                    continue;

                int indexInfirst = firstSchema.LookupColumnIndex(variableNames[i]);
                int indexInSecond = secondSchema.LookupColumnIndex(variableNames[i]);
                NetCDFDataType varType = TypeMapper.GetNcType(firstSchema.GetColumnType(indexInfirst));
                for (int x = 0; x < xLen; x++)
                {
                    for (int y = 0; y < yLen; y++)
                    {
                        for (int z = 0; z < zLen; z++)
                        {
                            for (int t = 0; t < tLen1; t++)
                            {
                                // Assumptions:
                                //       1. Set the operation only if the data is defined (not null).
                                //          Data must be defiend in both hypercubes. Is not keep it 'null'
                                //          ideally - the writer activity must be aware of how to handle null values!
                                HyperCubeDataPoint dpFirst = cube1[x, y, z, t];
                                HyperCubeDataPoint dpSecond = cube2[x, y, z, t];

                                if (dpFirst != null && dpSecond != null &&
                                    dpFirst[indexInfirst] != null && dpSecond[indexInSecond] != null)
                                {
                                    switch (operationToDo)
                                    {
                                        case AlgebraOperations.Addition:
                                            #region Casting and Addition
                                            switch (varType)
                                            {
                                                case NetCDFDataType.NcByte:
                                                    resultHC.SetData((byte)dpSecond[indexInSecond] + (byte)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcShort:
                                                    resultHC.SetData((short)dpSecond[indexInSecond] + (short)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcInt:
                                                    resultHC.SetData((int)dpSecond[indexInSecond] + (int)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcFloat:
                                                    resultHC.SetData((float)dpSecond[indexInSecond] + (float)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcDouble:
                                                    resultHC.SetData((double)dpSecond[indexInSecond] + (double)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                            }
                                            #endregion
                                            break;
                                        case AlgebraOperations.Difference:
                                            #region Casting and Addition
                                            switch (varType)
                                            {
                                                case NetCDFDataType.NcByte:
                                                    resultHC.SetData((byte)dpSecond[indexInSecond] - (byte)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcShort:
                                                    resultHC.SetData((short)dpSecond[indexInSecond] - (short)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcInt:
                                                    resultHC.SetData((int)dpSecond[indexInSecond] - (int)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcFloat:
                                                    resultHC.SetData((float)dpSecond[indexInSecond] - (float)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                                case NetCDFDataType.NcDouble:
                                                    resultHC.SetData((double)dpSecond[indexInSecond] - (double)dpFirst[indexInfirst],
                                                                        x, y, z, t, i);
                                                    break;
                                            }
                                            break;
                                            #endregion
                                        default:
                                            throw new ArgumentException("Algebra operation not supported");
                                    }
                                }
                            }
                        }
                    }
                }
            }

            #endregion

            #region 6. Preparations for returning data
            // Create an NetCDFFile object that can be returned
            return resultHC;

            #endregion
        }

        #endregion
    }

    class CleaningTransform : IHyperCubeTransform
    {

        #region IHyperCubeTransform Members

        HyperCube IHyperCubeTransform.Transform(params object[] parameters)
        {

            if (parameters.Length < 4)
                throw new ArgumentException("Incorrect arguments for Cleaning Transform");

            HyperCube cube1 = parameters[0] as HyperCube;
            if (cube1 == null)
                throw new ArgumentException("Unexpected arguments for transform");

            string variableName = parameters[1] as string;

            string fValue = parameters[2] as string;
            string tValue = parameters[3] as string;


            object fromValue = null;
            object toValue = null;


            int colIndex = 0;
            try
            {
                colIndex = cube1.Schema.LookupColumnIndex(variableName);

                NetCDFDataType dataType = TypeMapper.GetNcType(cube1.Schema.GetColumnType(colIndex));
                switch (dataType)
                {
                    case NetCDFDataType.NcByte:
                        //Try to cast the values in appropriate type
                        //if cast is not success, then throw error.
                        byte fValue1 = Convert.ToByte(fValue);
                        byte tValue1 = Convert.ToByte(tValue);

                        //Success

                        fromValue = (object)fValue1;
                        toValue = (object)tValue1;

                        break;
                    case NetCDFDataType.NcChar:
                        char fValue2 = Convert.ToChar(fValue);
                        char tValue2 = Convert.ToChar(tValue);
                        //Success

                        fromValue = (object)fValue2;
                        toValue = (object)tValue2;
                        break;
                    case NetCDFDataType.NcShort:
                        short fValue3 = Convert.ToInt16(fValue);
                        short tValue3 = Convert.ToInt16(tValue);
                        //Success

                        fromValue = (object)fValue3;
                        toValue = (object)tValue3;
                        //short
                        break;
                    case NetCDFDataType.NcInt:
                        int fValue4 = Convert.ToInt32(fValue);
                        int tValue4 = Convert.ToInt32(tValue);
                        //Success

                        fromValue = (object)fValue4;
                        toValue = (object)tValue4;
                        break;
                    case NetCDFDataType.NcFloat:
                        float fValue5 = Convert.ToSingle(fValue);
                        float tValue5 = Convert.ToSingle(tValue);
                        //Success

                        fromValue = (object)fValue5;
                        toValue = (object)tValue5;
                        //Float
                        break;
                    case NetCDFDataType.NcDouble:
                        double fValue6 = Convert.ToDouble(fValue);
                        double tValue6 = Convert.ToDouble(tValue);

                        //Success

                        fromValue = (object)fValue6;
                        toValue = (object)tValue6;
                        break;
                }
            }
            catch (Exception )
            {
                throw;
            }


            //Validation Complete, Now Find the value and replace this

            for (int a = 0; a < cube1.GetAxisLength(HyperCube.Axis.X); a++)
            {
                for (int b = 0; b < cube1.GetAxisLength(HyperCube.Axis.Y); b++)
                {
                    for (int c = 0; c < cube1.GetAxisLength(HyperCube.Axis.Z); c++)
                    {
                        for (int d = 0; d < cube1.GetAxisLength(HyperCube.Axis.T); d++)
                        {
                            HyperCubeDataPoint dataPoint = cube1[a, b, c, d];

                            if (dataPoint != null)
                            {
                                if (dataPoint[colIndex] != null)
                                {
                                    object dataValue = (object)dataPoint[colIndex];

                                    if (dataValue.Equals(fromValue))
                                    {
                                        cube1.SetData(toValue, a, b, c, d, colIndex);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            //NetCDFFile resultFile = new NetCDFFile(inputFile.NetCDFMetadata, cube1);
            return cube1;
        }

        #endregion

    }

    class ClampingTransform : IHyperCubeTransform
    {

        #region IHyperCubeTransform Members
		/// <summary>
		/// This Function does inpace Claming transformation for Hypercube.
		/// Inplace means that the input cube is modified.
		/// </summary>
		/// <param name="inputFile"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
        HyperCube IHyperCubeTransform.Transform(params object[] parameters)
        {
            if (parameters.Length != 2)
                throw new ArgumentException("Incorrect arguments for Clamping Transform");

            HyperCube cube1 = parameters[0] as HyperCube;
            if (cube1 == null)
                throw new ArgumentException("Unexpected arguments for transform");

            ClampingVariable[] listOfVariables = parameters[1] as ClampingVariable[];

            if (listOfVariables == null)
                throw new ArgumentException("array of ClampingVariable is required as second argument.");

            //HyperCube inputCube = inputFile.HyperCube;

            object[] minRanges = new object[listOfVariables.Length];
            object[] maxRanges = new object[listOfVariables.Length];
            object[] optionalValues = new object[listOfVariables.Length];


            //Check if the variable exists and has correct data type
            int[] columnCollection = new int[listOfVariables.Length];

            int loopCounter = 0;
           
            try
            {
                foreach (ClampingVariable myVariable in listOfVariables)
                {
                    columnCollection[loopCounter] = cube1.Schema.LookupColumnIndex(myVariable.VariableName);
                    //If exists, then execution will continue,
                    //else an exception will be raised and code execution will stop

                    //Column Exists, Check the datatype
                    NetCDFDataType dataType = TypeMapper.GetNcType(cube1.Schema.GetColumnType(columnCollection[loopCounter]));

                    #region Switch Case for Data Type
                    switch (dataType)
                    {
                        case NetCDFDataType.NcByte:
                            //Try to cast the values in appropriate type
                            //if cast is not success, then throw error.
                            
                            byte minR1 = Convert.ToByte(myVariable.MinValue);
                            byte maxR1 = Convert.ToByte(myVariable.MaxValue);

                            if (maxR1 < minR1)
                                throw new ArgumentException("max range is less than min range");
                            //If optional value is defined, it should also be of same type
                            if (!string.IsNullOrEmpty(myVariable.OptionalValue))
                            {
                                byte opt1 = Convert.ToByte(myVariable.OptionalValue);
                                optionalValues[loopCounter] = (object)opt1;
                            }
                            else
                            {
                                optionalValues[loopCounter] = null;
                            }

                            //Success

                            minRanges[loopCounter] = (object)minR1;
                            maxRanges[loopCounter] = (object)maxR1;

                            break;
                        case NetCDFDataType.NcShort:
                            short minR3 = Convert.ToInt16(myVariable.MinValue);
                            short maxR3 = Convert.ToInt16(myVariable.MaxValue);
                            if (maxR3 < minR3)
                                throw new ArgumentException("max range is less than min range");
                            //If optional value is defined, it should also be of same type
                            if (!string.IsNullOrEmpty(myVariable.OptionalValue))
                            {
                                short opt1 = Convert.ToInt16(myVariable.OptionalValue);
                                optionalValues[loopCounter] = (object)opt1;
                            }
                            else
                            {
                                optionalValues[loopCounter] = null;
                            }
                            //Success

                            minRanges[loopCounter] = (object)minR3;
                            maxRanges[loopCounter] = (object)maxR3;
                            break;
                        case NetCDFDataType.NcInt:
                            int minR4 = Convert.ToInt32(myVariable.MinValue);
                            int maxR4 = Convert.ToInt32(myVariable.MaxValue);
                            
                            if (maxR4 < minR4)
                                throw new ArgumentException("max range is less than min range");

                            //If optional value is defined, it should also be of same type
                            if (!string.IsNullOrEmpty(myVariable.OptionalValue))
                            {
                                int opt1 = Convert.ToInt32(myVariable.OptionalValue);
                                optionalValues[loopCounter] = (object)opt1;
                            }
                            else
                            {
                                optionalValues[loopCounter] = null;
                            }
                            //Success

                            minRanges[loopCounter] = (object)minR4;
                            maxRanges[loopCounter] = (object)maxR4;
                            break;
                        case NetCDFDataType.NcFloat:
                            float minR5 = Convert.ToSingle(myVariable.MinValue);
                            float maxR5 = Convert.ToSingle(myVariable.MaxValue);

                            if (maxR5 < minR5)
                                throw new ArgumentException("max range is less than min range");

                            //If optional value is defined, it should also be of same type
                            if (!string.IsNullOrEmpty(myVariable.OptionalValue))
                            {
                                float opt1 = Convert.ToSingle(myVariable.OptionalValue);
                                optionalValues[loopCounter] = (object)opt1;
                            }
                            else
                            {
                                optionalValues[loopCounter] = null;
                            }
                            //Success

                            minRanges[loopCounter] = (object)minR5;
                            maxRanges[loopCounter] = (object)maxR5;
                            //Float
                            break;
                        case NetCDFDataType.NcDouble:
                            double minR6 = Convert.ToDouble(myVariable.MinValue);
                            double maxR6 = Convert.ToDouble(myVariable.MaxValue);
                            
                            if (maxR6 < minR6)
                                throw new ArgumentException("max range is less than min range");
                            
                            //If optional value is defined, it should also be of same type
                            if (!string.IsNullOrEmpty(myVariable.OptionalValue))
                            {
                                double opt1 = Convert.ToDouble(myVariable.OptionalValue);
                                optionalValues[loopCounter] = (object)opt1;
                            }
                            else
                            {
                                optionalValues[loopCounter] = null;
                            }
                            //Success
                            minRanges[loopCounter] = (object)minR6;
                            maxRanges[loopCounter] = (object)maxR6;
                            break;
                        default:
                            throw new ArgumentException("Only numeric types are supported in this activity");
                            

                    } //End Switch
                    #endregion

                    loopCounter++; //Increment the loopCounter
                }
            }
            catch (Exception)
            {
                throw;
            }

            //Validation Completed ie Variable exists and data types are appropriate

			#region Retrieve missing values for variables
			object[] missingValues = new object[listOfVariables.Length];
			loopCounter = 0;
            foreach (ClampingVariable myVariable in listOfVariables)
            {
				int variableIndex = cube1.Schema.LookupColumnIndex(myVariable.VariableName);
				NetCDFDataType dataType = TypeMapper.GetNcType(cube1.Schema.GetColumnType(variableIndex));

				//first get the missing value of the variable
                object attVal = cube1.Schema.GetAttribute(variableIndex, ScalerTransform.MissingValue);
                if (attVal != null)
				{
                    if (attVal is byte[] || attVal is char[] ||
                        attVal is short[] || attVal is int[] ||
                        attVal is float[] || attVal is double[])
					{
                        switch (TypeMapper.GetNcFileType(cube1.Schema.GetColumnType(variableIndex)))
						{
							case NcType.NcByte:
                                missingValues[loopCounter] = (attVal as byte[])[0];
								break;
							case NcType.NcDouble:
                                missingValues[loopCounter] = (attVal as double[])[0];
								break;
							case NcType.NcFloat:
                                missingValues[loopCounter] = (attVal as float[])[0];
								break;
							case NcType.NcInt:
                                missingValues[loopCounter] = (attVal as int[])[0];
								break;
							case NcType.NcShort:
                                missingValues[loopCounter] = (attVal as short[])[0];
								break;
							default:
								throw new ArgumentException("Only numeric types are supported in this activity");
						}
					}
					else
					{
                        missingValues[loopCounter] = attVal;
					}
				}
                loopCounter++; //Increment the loopCounter
            }
			#endregion

			// Now clamp the hypercubes values...
            for (int v = 0; v < columnCollection.Length; v++)
            {
                int index = columnCollection[v];
                NetCDFDataType dataType = TypeMapper.GetNcType(cube1.Schema.GetColumnType(index));

                for (int a = 0; a < cube1.GetAxisLength(HyperCube.Axis.X); a++)
                {
                    for (int b = 0; b < cube1.GetAxisLength(HyperCube.Axis.Y); b++)
                    {
                        for (int c = 0; c < cube1.GetAxisLength(HyperCube.Axis.Z); c++)
                        {
                            for (int d = 0; d < cube1.GetAxisLength(HyperCube.Axis.T); d++)
                            {
                                HyperCubeDataPoint dataPoint = cube1[a, b, c, d];
                                if (dataPoint != null)
                                {
									if (dataPoint[index] != null && !ClampingTransform.IsEqual(dataPoint[index], missingValues[v], dataType ))
                                    {
                                       object oldValue = (object)dataPoint[index];
                                       object newValue;
                                       if (ReplaceValue(minRanges[v], maxRanges[v],
                                           optionalValues[v], oldValue, dataType, out newValue))
                                       {
                                           cube1.SetData(newValue, a, b, c, d, index);
                                       }
                                    }
                                }
                            }//d
                        }//c
                    }//b
                }//a
            }//v

            return cube1;
        }

		private static bool IsEqual(object data, object missing, NetCDFDataType dataType)
		{
			if (missing == null)
				return false;

			switch (dataType)
			{
				case NetCDFDataType.NcByte:
					return (byte)data == (byte)missing;
				case NetCDFDataType.NcChar:
					return (char)data == (char)missing;
				case NetCDFDataType.NcDouble:
					return (double)data == (double)missing;
				case NetCDFDataType.NcFloat:
					return (float)data == (float)missing;
				case NetCDFDataType.NcInt:
					return (int)data == (int)missing;
				case NetCDFDataType.NcShort:
					return (short)data == (short)missing;
				default:
					throw new NotImplementedException("data type not supported");
			}
		}

        #endregion

        private bool ReplaceValue(object minValue, object maxValue, object optValue,object existingValue, NetCDFDataType dataType, out object newValue)
        {
            newValue = null;
            bool replace = true;
            switch (dataType)
            {
                case NetCDFDataType.NcByte:
                    
                    if (Convert.ToByte(existingValue) <= Convert.ToByte(minValue))
                    {
                        if(optValue!= null)
                        {
                            newValue = Convert.ToByte(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToByte(minValue);
                        }
                    }
                    else if (Convert.ToByte(existingValue) > Convert.ToByte(maxValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToByte(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToByte(maxValue);
                        }
                    }
                    else
                    {
                        //Value lies in the range
                        replace = false;
                    }
                    break;

                case NetCDFDataType.NcShort:
                    if (Convert.ToInt16(existingValue) <= Convert.ToInt16(minValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToInt16(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToInt16(minValue);
                        }
                    }
                    else if (Convert.ToInt16(existingValue) > Convert.ToInt16(maxValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToInt16(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToInt16(maxValue);
                        }
                    }
                    else
                    {
                        replace = false;
                    }
                    break;
                case NetCDFDataType.NcInt:
                    if (Convert.ToInt32(existingValue) <= Convert.ToInt32(minValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToInt32(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToInt32(minValue);
                        }
                    }
                    else if (Convert.ToInt32(existingValue) > Convert.ToInt32(maxValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToInt32(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToInt32(maxValue);
                        }
                    }
                    else
                    {
                        replace = false;
                    }
                    break;
                case NetCDFDataType.NcFloat:
                    if (Convert.ToSingle((existingValue)) <= Convert.ToSingle((minValue)))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToSingle(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToSingle(minValue);
                        }
                    }
                    else if (Convert.ToSingle(existingValue) > Convert.ToSingle(maxValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToSingle(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToSingle(maxValue);
                        }
                    }
                    else
                    {
                        replace = false;
                    }
                    break;
                case NetCDFDataType.NcDouble:
                    if (Convert.ToDouble(existingValue) <= Convert.ToDouble(minValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToDouble(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToDouble(minValue);
                        }
                    }
                    else if (Convert.ToDouble(existingValue) > Convert.ToDouble(maxValue))
                    {
                        if (optValue != null)
                        {
                            newValue = Convert.ToDouble(optValue);
                        }
                        else
                        {
                            newValue = Convert.ToDouble(maxValue);
                        }
                    }
                    else
                    {
                        replace = false;
                    }
                    break;
            }

            return replace;

        }
    }

	class ScalerTransform : IHyperCubeTransform
	{
		#region Private Fields

		//Used to get the variable attribute of this name.
		public const string MissingValue = "missing_value";

		#endregion

		#region IHyperCubeTransform Members

		public HyperCube Transform(params object[] parameters)
		{

			#region Input arguments validations

			if (parameters.Length != 3)
				throw new ArgumentException("Three parameters required");

            HyperCube cube1 = parameters[0] as HyperCube;
            if (cube1 == null)
                throw new ArgumentException("Unexpected arguments for transform");

            double scalerValue = 1.0d; // (double)parameters[0]; // as double;
			try
			{
				scalerValue = (double)parameters[1];
			}
			catch (InvalidCastException invalidCast)
			{
				throw invalidCast;
			}

			string variableName = parameters[2] as string;
			if (variableName == null)
				throw new ArgumentException("string value is expected as variable name parameter");

			#endregion

			// 1. Validations.
			// 2. Create a new schema with variables in the variableNames array.
			// 3. Create new hyper cube.
			// 5. Return - (No need to update metadata).

			HyperCube resultCube = cube1;

			// Determine the size of the HyperCube
			int xLen = resultCube.GetAxisLength(HyperCube.Axis.X);
			int yLen = resultCube.GetAxisLength(HyperCube.Axis.Y);
			int zLen = resultCube.GetAxisLength(HyperCube.Axis.Z);
			int tLen = resultCube.GetAxisLength(HyperCube.Axis.T);

			// Populate the data in the output HyperCube
			int variableIndex = resultCube.Schema.LookupColumnIndex(variableName);
			if (variableIndex >= 0)
			{
				//first get the missing value of the variable
				string varName = resultCube.Schema.GetColumnName(variableIndex);
                object missingValue = resultCube.Schema.GetAttribute(variableIndex, ScalerTransform.MissingValue);
                //NcVar var = netCDFFile.NcMetaData.GetVariable(varName);
                //NcAtt att = var.GetAtt(MissingValue);
                //if (att != null)
                //{
                //    missingValue = (object)att.Value.DataValue;
                //}


				for (int x = 0; x < xLen; x++)
				{
					for (int y = 0; y < yLen; y++)
					{
						for (int z = 0; z < zLen; z++)
						{
							for (int t = 0; t < tLen; t++)
							{
								object value = cube1[x, y, z, t][variableIndex];
                                cube1.SetData(GetScaledValue(value, scalerValue, missingValue), x, y, z, t, variableIndex);
							}
						}
					}
				}
			}
			//netCDFFile.HyperCube = resultCube;
            return cube1;
		}

		#endregion

		#region Scale HyperCube Values

		/// <summary>
		/// Scales the values as per the scale provided
		/// </summary>
		/// <param name="currentValue"></param>
		/// <param name="scale"></param>
		/// <returns></returns>
		private object GetScaledValue(object currentValue, double scale, object missingValue)
		{
			try
			{
				if (currentValue is int)
				{
					int temp = (int)currentValue;

					if (missingValue != null)
					{
						// Here temp should be compared with "Missing Value" of the variable if present
						int[] missing = (int[])missingValue;
						if (temp == missing[0])
							return missing[0];  //Ignore scaling
					}

					return (int)(temp * scale);
				}
				else if (currentValue is float)
				{
					float temp = (float)currentValue;

					if (missingValue != null)
					{
						// Here temp should be compared with "Missing Value" of the variable if present
						float[] missing = (float[])missingValue;
						if (temp == missing[0])
							return missing[0];  //Ignore scaling
					}

					return (float)(temp * scale);
				}
				else if (currentValue is short)
				{
					short temp = (short)currentValue;

					if (missingValue != null)
					{
						// Here temp should be compared with "Missing Value" of the variable if present
						short[] missing = (short[])missingValue;
						if (temp == missing[0])
							return missing[0];  //Ignore scaling
					}

					return (short)(temp * scale);
				}
				else if (currentValue is double)
				{
					double temp = (double)currentValue;

					if (missingValue != null)
					{
						// Here temp should be compared with "Missing Value" of the variable if present
						double[] missing = (double[])missingValue;
						if (temp == missing[0])
							return missing[0];  //Ignore scaling
					}

					return (temp * scale);
				}
				return null;
			}
			catch (InvalidCastException)
			{
				return null;
			}

		}

		#endregion

	}

}
